Skip to content

M33: Snake & MountainCar AI run fully in the browser (zero server inference)#24

Merged
PieterjanDeClippel merged 9 commits into
masterfrom
snake-clientside-ai
Jul 5, 2026
Merged

M33: Snake & MountainCar AI run fully in the browser (zero server inference)#24
PieterjanDeClippel merged 9 commits into
masterfrom
snake-clientside-ai

Conversation

@PieterjanDeClippel

Copy link
Copy Markdown
Member

Extends the FruitCake M32 pattern to the two remaining WebSocket-AI games. After this, no game's "watch AI" touches the server — every one runs client-side, and there is no server WebSocket left in the app.

What's in here

  • Snake — full Polyglot client-side port. snake_solver.pg single-sources the dynamics + 177-dim egocentric observation + the action mask including the anti-self-trap flood-fill shield + PgSnakeNet + a masked-greedy chooseAction. C# SnakeEnv is now a facade over it. Browser: snake-net.ckpt + snake-net.ts parser + SnakeDirector.
  • MountainCar — full Polyglot client-side port. mountaincar_solver.pg single-sources the classic-control dynamics (cos) + normalised 2-dim observation + the PPO policy PgMlpNet (Linear+Tanh hidden, linear output, argmax). C# MountainCarEnv is a facade. Browser: mountaincar-net.ckpt + mountaincar-net.ts (parseMlp) + MountainCarDirector.
  • Server path retired for both: controllers, model services (+ Program.cs regs), the two *-api.ts socket clients, the stale models/*.ckpt, the API tests, the shared EpisodeStreamer, and app.UseWebSockets() — all removed. The only server AI surfaces left are the request/response REST solvers (2048/RushHour/Cube).

Why it matters

Per-viewer server inference/streaming for Snake + MountainCar → zero (a one-time, CDN-cached weights download each). Combined with M32 (FruitCake), the whole playground's live "watch AI" is now client-side.

Design notes

  • The env dynamics + observation + net forward are single-sourced in each game's .pg (transpiled to C# at build + committed TS), so the C# training/eval env and the browser share one source. C# facades re-add host concerns (the IEnvironment API, the food/start RNG kept host-side for determinism, state I/O, the throw-on-illegal-action contract).
  • Snake is pure integer math → C#↔TS byte-identical. MountainCar uses cos/tanh (Polyglot 0.3.0+); the C# side stays exact (Math.Cos), the browser differs by ≤1 ULP which is harmless (argmax decision, no server twin).
  • Net-forward is duplicated per game (PgSnakeNet = a renamed copy of FruitCake's dueling net; PgMlpNet new) rather than a shared nn.pg — fine for two games.

Verification

  • Per-game equivalence via the existing C# tests (the facades keep them green): 9 SnakeEnv + 6 MountainCarEnv (incl. the Gymnasium golden dynamics) + 22 FruitCake/Polyglot + parity = 38 green; web builds clean.
  • In-browser (host + Playwright): Snake plays (ate 40, length ~35); MountainCar's PPO agent swings up and reaches the flag. Both: 0 console errors, weights fetched once from /…-net.ckpt, 0 /api/{snake,mountaincar} calls.
  • Deploy path is the same LFS-asset flow M32 verified (lfs: true checkout + Dockerfile COPY ClientApp/ before npm build; Angular serves public/**).

Upstream (MintPlayer.Polyglot)

Bumped 0.1.4 → 0.3.1. Filed and got fixed: #11 (transcendentals + module linking + i32() wrap, in 0.3.0) and #14 (multi-.pg prelude collision, in 0.3.1). Worked around a net-agnostic evolving-any TS7022 (Snake BFS → relaxation).

Test plan

  • dotnet test env + Polyglot filter (38 green)
  • web builds; Angular dev server + prod build compile clean
  • Playwright: Snake + MountainCar play client-side, 0 console errors, 0 /api/* AI calls
  • Gymnasium golden dynamics preserved (MountainCar), tail-follow occupancy invariant (Snake)

🤖 Generated with Claude Code

PieterjanDeClippel and others added 9 commits July 5, 2026 20:46
…glot 0.1.4->0.3.0

3-agent investigation + Polyglot 0.3.0 verification. 0.3.0 fixes all of #11 (transcendentals
added, module imports now link, i32() TS wrap fixed) and #9 stays fixed — so MountainCar's
transcendental fork dissolves (cos/tanh writable in a .pg; sub-ULP C#<->TS drift harmless
post-cutover). FruitCake TS codegen verified content-identical on 0.3.0 (22 tests green).
Filed remaining gaps as MintPlayer.Polyglot#11 (all addressed in 0.3.0). Plan: Snake first
(clean full-Polyglot port), MountainCar second (now also uniform Polyglot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntPlayer.Polyglot#14

snake_solver.pg ports SnakeEnv (dynamics + 177-dim observation + action mask incl. the
anti-self-trap flood-fill shield) over flat Lists (body head-at-end, occupancy bool-list,
BFS via list+cursor — no HashSet/LinkedList/Queue), plus PgDuelingNet + a masked-greedy
chooseAction. It type-checks and generates correct C#/TS individually.

BLOCKED from building alongside FruitCake by MintPlayer.Polyglot#14: with a 2nd .pg each
transpiled .cs re-emits the prelude (Option/Some/None) → CS0101. Parked via PolyglotFile
Remove so the branch stays green; delete the Remove + continue (facade/tests/director) once
the prelude is deduped upstream. Env build green with Snake excluded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…longside FruitCake

Bump 0.3.0->0.3.1 (shared __polyglot_prelude.cs + partial PolyglotProgram fix #14), drop
the PolyglotFile Remove workaround. Fixes to snake_solver.pg: rename PgDuelingNet->PgSnakeNet
(distinct name; a deliberate copy of FruitCake's net — one assembly, so a shared name would
clash; a shared nn.pg is a future refactor), and rename a fill-loop var to avoid a C# i-scope
clash (CS0136). Environments builds with both .pg; 22 FruitCake/Polyglot tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (SN1-SN3)

Rewrite the C# SnakeEnv to delegate to the transpiled PgSnakeEnv (dynamics + 177-dim
observation + action mask incl. the flood-fill shield now live once in snake_solver.pg,
shared with the browser's generated snake_solver.ts). The facade re-adds host concerns:
IEnvironment/IStatefulEnvironment API, the food RNG (Xoshiro, kept out of the single
source via the core's needsFood signal so determinism holds), the head-first Body/state
format, and the throw-on-illegal-action contract. Also commit the generated snake_solver.ts.

All 9 SnakeEnvTests green (dynamics, tail-follow occupancy sync, eating/wall/mask/throw,
same-seed obs, save/restore round-trip); Occupied() helper updated to read the core's
occupancy list. Full solution builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… retire server

SN4: ship the 177-dim net as ClientApp/public/snake-net.ckpt (LFS) + snake-net.ts parser
(builds PgSnakeNet from the dueling-q checkpoint).
SN5: SnakeDirector runs the whole AI in the browser — PgSnakeEnv dynamics + the flood-fill
action mask + masked-greedy chooseAction over the loaded net, on a discrete tick. Rewire the
Snake component's watch mode to it (drop the WebSocket/SnakeApi); both modes now run in-browser.
SN6: delete SnakeController, SnakeModelService (+ Program.cs regs), snake-api.ts, the stale
models/snake.dqn.ckpt, and SnakeApiTests (server WS/status path). EpisodeStreamer kept
(MountainCar still uses it). Web builds; 32 Snake/FruitCake/Polyglot tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ny TS7022)

The BFS queue (List<i32> whose pushed values derive from reading the same list) transpiled
to an untyped `let queue = []` with circular element inference -> TS7022 in the browser
build. Rewrite reachableFreeSpace as iterative relaxation over the `seen` bool-list
(BFS-equivalent; values come from a fixed 0..cells range, never a list read). Snake grids
are small so O(cells^2) is fine. C# + TS regen; 9 SnakeEnvTests green (obs/mask unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Snake ships fully client-side (branch snake-clientside-ai): env+obs+mask+net single-sourced
in snake_solver.pg, C# SnakeEnv facade, browser director, server path retired. Verified in
browser (AI ate 40 / length ~35, 0 console errors, 0 /api/snake calls). MountainCar remains
(now uniform-Polyglot since 0.3.0 added cos/tanh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lot, retire server

mountaincar_solver.pg single-sources the classic-control dynamics (cos), the normalised
2-dim observation, and the PPO policy forward (PgMlpNet: Linear+Tanh hidden, linear output,
argmax) — cos/tanh now available in Polyglot 0.3.0+ (sub-ULP C#<->TS drift is harmless: no
server twin, argmax decision; C# stays exact via Math.Cos). C# MountainCarEnv is a facade
over PgMountainCarEnv (6 MountainCarEnvTests green, incl. the Gymnasium golden dynamics).

Browser: ship models/mountaincar.ppo.ckpt -> public/mountaincar-net.ckpt (LFS) + a mlp
.ckpt parser (mountaincar-net.ts -> PgMlpNet) + MountainCarDirector; rewire the watch mode
off the WebSocket. Retire the server path: MountainCarController, MountainCarModelService
(+ Program.cs regs), mountaincar-api.ts, stale net, MountainCarApiTests, and EpisodeStreamer
(now unused). Removed app.UseWebSockets() — no server WS remains (all games client-side).
Web builds; 38 env/Polyglot tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ainCar)

Both remaining WebSocket-AI games now run entirely in the browser; no server WebSocket
remains anywhere (EpisodeStreamer + UseWebSockets removed). Verified in-browser (Snake
ate 40; MountainCar reaches the flag). PRD status -> IMPLEMENTED; PLAN M33 both games done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@PieterjanDeClippel PieterjanDeClippel merged commit 3d101fe into master Jul 5, 2026
2 checks passed
@PieterjanDeClippel PieterjanDeClippel deleted the snake-clientside-ai branch July 5, 2026 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant